package com.chen.common.satoken.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 注册 Sa-Token 拦截器，打开注解式鉴权功能
 * todo 配置server.servlet.context-path 会导致 @SaIgnore注解失效
 * 		及拦截器失效???
 * @author ChenJinLong
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "sa-token.ex")
public class SaTokenWebMvcConfig implements WebMvcConfigurer, ApplicationRunner {

    /** 忽略鉴权的url */
    private List<String> ignores;
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
    private static InterceptorRegistration interceptorRegistration;
    private static final List<String> IGNORE_URLS = new ArrayList<>();

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器，打开注解式鉴权功能
        InterceptorRegistration registration = registry
                .addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**");

        if (CollectionUtil.isNotEmpty(ignores)){
            registration.excludePathPatterns(ignores);
        }
//        SaTokenWebMvcConfig.interceptorRegistration = registration;
    }

    @Override
    public void run(ApplicationArguments args) {
//        RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping)SpringContextHolder.getAppContext().getBean("requestMappingHandlerMapping");
//        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
//
//        for (RequestMappingInfo info : map.keySet()) {
//            HandlerMethod handlerMethod = map.get(info);
//
//			// 1. 首先获取类上边 @SaIgnore 注解
//            SaIgnore controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), SaIgnore.class);
//
//			// 2. 当类上不包含 @SaIgnore 注解则获取该方法的注解
//			if (controller == null) {
//                SaIgnore method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), SaIgnore.class);
//				Optional.ofNullable(method).ifPresent(inner -> info.getPatternsCondition().getPatterns()
//						.forEach(url -> this.filterPath(url, info, map)));
//				continue;
//			}
//
//			// 3. 当类上包含 @SaIgnore 注解 判断handlerMethod 是否包含在 SaIgnore 类中
//			Class<?> beanType = handlerMethod.getBeanType();
//			Method[] methods = beanType.getDeclaredMethods();
//			Method method = handlerMethod.getMethod();
//			if (ArrayUtil.contains(methods, method)) {
//				info.getPatternsCondition().getPatterns().forEach(url -> filterPath(url, info, map));
//			}
//        }
//
//        SaTokenWebMvcConfig.interceptorRegistration.excludePathPatterns(IGNORE_URLS);
    }

    /**
	 * 过滤 SaIgnore 设置
	 * <p>
	 * 0. 暴露安全检查 1. 路径转换： 如果为restful(/xx/{xx}) --> /xx/* ant 表达式 2.
	 * 构建表达式：允许暴露的接口|允许暴露的方法类型,允许暴露的方法类型 URL|GET,POST,DELETE,PUT
	 * </p>
	 * @param url mapping路径
	 * @param info 请求犯法
	 * @param map 路由映射信息
	 */
	private void filterPath(String url, RequestMappingInfo info, Map<RequestMappingInfo, HandlerMethod> map) {
		List<String> methodList = info.getMethodsCondition().getMethods().stream().map(RequestMethod::name)
				.collect(Collectors.toList());
		String resultUrl = ReUtil.replaceAll(url, PATTERN, "*");
		if (CollUtil.isEmpty(methodList)) {
			IGNORE_URLS.add(resultUrl);
		} else {
			IGNORE_URLS.add(String.format("%s|%s", resultUrl, CollUtil.join(methodList, StrUtil.COMMA)));
		}
	}
}
